/*
 * Copyright 2006-2009 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package asphalt2.action
{
	import asphalt2.action.error.IErrorDisplay;
	import asphalt2.action.error.NoOpErrorDisplay;
	import asphalt2.action.error.TooltipErrorDisplay;
	import asphalt2.control.AbstractControl;
	import asphalt2.event.ModulePageContainerEvent;
	import asphalt2.event.PageContainerEvent;
	import asphalt2.util.UIUtil;
	import asphalt2.validator.IValidateError;
	
	import flash.display.DisplayObject;
	import flash.display.DisplayObjectContainer;
	import flash.events.Event;
	import flash.events.IEventDispatcher;
	
	import mx.events.CalendarLayoutChangeEvent;
	
	/**
	 * $Action
	 *
	 * @author c9katayama
	 */
	public class $Action extends AbstractAction
	{
		protected var _executeFunction:Function;
		
		protected var _validateFunction:Function;
			
		protected var _successFunction:Function;

		protected var _errorFunction:Function;

		protected var _disposeFunction:Function;
		
		protected var _transitionFunction:Function;
		
		protected var _errorDisplay:IErrorDisplay = createDefaultErrorDisplay();

		public function $Action(){
		}		
		/**
		 * イベント発生時に最初に呼ばれるFunctionをセットします。
		 **/
		public function execute(f:Function):$Action{
			_executeFunction = f;
			return this;
		}
		/**
		 * execute後に呼び出される、入力のValidateを行うFunctionをセットします。
		 * FunctionでValidateを行い、エラーがある場合はIValidateErrorを返します。
		 * @param Function 戻り値がIValidateErrorのFunction
		 **/
		public function validate(f:Function):$Action{
			this._validateFunction = f;
			return this;
		}
		/**
		 * validateFunctionの戻り値がnull、もしくはIValidateError#isError()がfalseの場合に
		 * 呼び出されるFunctionをセットします。
		 **/
		public function success(f:Function):$Action{
			this._successFunction = f;
			return this;
		}
		/**
		 * validateFunctionの戻り値のIValidateError#isError()がtrueの場合に
		 * 呼び出されるFunctionをセットします。
		 **/
		public function error(f:Function):$Action{
			this._errorFunction = f;
			return this;
		}
		/**
		 * successの後に呼び出されるFunctionをセットします。
		 * 終了処理を行います。
		 **/
		public function dispose(f:Function):$Action{
			this._disposeFunction = f;
			return this;
		}
		/**
		 * 画面遷移を行うFunctionをセットします。
		 **/
		public function transition(f:Function):$Action{
			this._transitionFunction = f;
			return this;
		}
		/**
		 * エラー表示用のIErrorDisplayをセットします。
		 * セットしない場合、createDefaultErrorDisplay()で生成される
		 * IErrorDisplayを使用します。
		 */
		public function errorDisplay(errorDisplay:IErrorDisplay):$Action{
			if(errorDisplay == null){
				errorDisplay = new NoOpErrorDisplay();
			}else{
				this._errorDisplay = errorDisplay;
			}
			return this;
		}
		/**
		 * 処理成功時に遷移する先のページを設定します。
		 * @param pageClass 遷移先のクラス
		 * @param pageContainerId ページを表示するPageContainerId。nullの場合、イベント発生した
		 *                        コンポーネントから一番近いPageContainerをセットします。
		 * @param 次のページに引き継ぐパラメータ
		 *        次のページにパラメータのキー値と同じ名前で変数宣言すると、parametersから同じ名前のパラメータを
		 *        取得してセットします。また「parameters」という名前の変数を宣言しておくと、parametersそのものを
		 *        セットします。
		 **/
		public function nextPage(pageClass:Class,pageContainerId:String=null,parameters:Object=null):$Action{
			_transitionFunction = 
				createPageTransitionFunction(pageClass,pageContainerId,parameters);
			return this;
		}
		/**
		 * Moduleをロードし、画面遷移を行います。
		 **/
		public function nextModulePage(modulePath:String,pageContainerId:String=null,
													parameters:Object=null,nextPageClassName:String=null):$Action{
			_transitionFunction = 
				createModulePageTransitionFunction(modulePath,
								pageContainerId,parameters,nextPageClassName);
			return this;
		}

		protected override function handleEvent(e:Event):void{
			_event = e;
			invokeNext(_executeFunction,executeEnd);
		}

		protected function executeEnd(result:*=null):void{
			invokeNext(_validateFunction,validateEnd);	
		}		

		protected function validateEnd(error:IValidateError=null):void{
			if(error==null || error.hasError() == false){
				_errorDisplay.resetErrorDisplay();	
				invokeNext(_successFunction,successEnd);
			}else{
				_errorDisplay.displayError(error,_event.target);
				invokeNext(_errorFunction,errorEnd);
			}
		}
		protected function successEnd(result:*=null):void{
			invokeNext(_disposeFunction,disposeEnd);
		}
		protected function errorEnd(result:*=null):void{

		}
		protected function disposeEnd(result:*=null):void{
			invokeNext(_transitionFunction,transitionEnd);
		}
		protected function transitionEnd(result:*=null):void{
			_event = null;
		}
				
		protected function createDefaultErrorDisplay():IErrorDisplay{
			return new TooltipErrorDisplay();
		}
		protected function findRecentParent():DisplayObjectContainer{
			var recentParent:DisplayObjectContainer;
			if(_event != null){
				var eventSource:DisplayObject = _event.target as DisplayObject;
				if(eventSource != null){
					if(eventSource is DisplayObjectContainer){
						recentParent = DisplayObjectContainer(eventSource);
					}else{
						recentParent = eventSource.parent;
					}
				}
			}
			if(recentParent == null){
				recentParent = UIUtil.findRoot() as DisplayObjectContainer;
			}
			return recentParent;
		}
		protected function createPageTransitionFunction(pageClass:Class,pageContainerId:String,parameters:Object):Function{
			return function():void{
				var recentParent:DisplayObjectContainer = findRecentParent();
				if(pageContainerId == null){
					pageContainerId = UIUtil.findRecentPageContainerId(recentParent);
				}
				var event:PageContainerEvent = new PageContainerEvent(pageContainerId,pageClass,parameters);
				recentParent.dispatchEvent(event);
				$handlerActionDispose();
			}			
		}
		protected function createModulePageTransitionFunction(
			modulePath:String,
			pageContainerId:String,
			parameters:Object,			
			nextPageClassName:String):Function{
			return function():void{
				var recentParent:DisplayObjectContainer = findRecentParent();
				if(pageContainerId == null){
					pageContainerId = UIUtil.findRecentPageContainerId(recentParent);
				}
				var event:ModulePageContainerEvent = new ModulePageContainerEvent(
					pageContainerId,modulePath,parameters,nextPageClassName);
				recentParent.dispatchEvent(event);
				$handlerActionDispose();
			}			
		}
		
		/**
		 * この$Actionを破棄するためのFunctionを、引数のdispatcherにセットします。
		 * デフォルトの画面遷移の場合は自動的に破棄されますが、それ以外の場合は明示的にハンドラを呼ぶことで
		 * 破棄できます。
		 */
		public function registerActionDisposeEvent(dispatcher:IEventDispatcher,eventType:String):$Action{
			UIUtil.addOnceEventListener(dispatcher,eventType,$handlerActionDispose);
			return this;
		}
		/**
		 * 引数のcontrolがREMOVEされたときにこのActionを破棄するための
		 * controlをセットします。
		 */
		public function registerActionDisposeControl(control:AbstractControl):$Action{
			UIUtil.addOnceEventListener(control,Event.REMOVED,$handlerActionDispose);
			return this;			
		}
		/**
		 * 引数のcomponentがREMOVEされたときにこのActionを破棄するための
		 * componentをセットします。
		 */
		public function registerActionDisposeComponent(component:DisplayObject):$Action{
			UIUtil.callBackWhenComponentRemoved(component,$handlerActionDispose);
			return this;
		}
		public function $handlerActionDispose(e:*=null):void{
			_executeFunction = null;
			_validateFunction = null;
			_successFunction = null;
			_errorFunction = null;
			_disposeFunction = null;
			_transitionFunction = null;
			_errorDisplay = null;						
		}
	}
}